package com.zoe.onelink.redis.lock.aspect;

import com.zoe.onelink.annotation.lock.DistributedLock;
import com.zoe.onelink.common.enumeration.ExceptionCodeEnum;
import com.zoe.onelink.common.exception.ApiException;
import com.zoe.onelink.redis.util.RedisLockUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <p>标题: </p>
 * <p>描述: </p>
 * <p>版权: Copyright (c) 2021</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2021-12-20
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
public class RedisLockAspect {

    @Around("@annotation(distributedLock)")
    public Object aspect(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        String lockId = UUID.randomUUID().toString();
        Object result;
        try {
            // 获取Redis锁
            this.tryLock(distributedLock, lockId);
            result = joinPoint.proceed();
        } finally {
            boolean unlockStatus = RedisLockUtil.unlock(distributedLock.key(), lockId);
            if (!unlockStatus) {
                log.error("[Redis分布式锁] 解锁失败,lockKey={},lockId={}", distributedLock.key(), lockId);
            }
        }
        return result;
    }

    /**
     * 尝试从Redis获取锁
     */
    private void tryLock(DistributedLock distributedLock, String lockId) throws InterruptedException {
        String lockKey = distributedLock.key();
        int expireSeconds = distributedLock.expireSeconds();
        boolean status = RedisLockUtil.tryLock(lockKey, lockId, expireSeconds, TimeUnit.SECONDS);
        // 如果获取成功直接返回
        if (status) {
            return;
        }
        // 获取失败时,重试次数,1秒一次
        int retryCount = distributedLock.waitSeconds();
        if (retryCount > 0) {
            for (int i = 0; i < retryCount; i++) {
                if (log.isDebugEnabled()) {
                    log.debug("[Redis分布式锁] 开始尝试第{}次获取锁,LockKey:{}", i + 1, lockKey);
                }
                Thread.sleep(1000);
                if (RedisLockUtil.tryLock(lockKey, lockId, expireSeconds, TimeUnit.SECONDS)) {
                    return;
                }
            }
            throw ApiException.createEx(ExceptionCodeEnum.REDIS_TRY_GET_LOCK_MAXIMUM_NUMBER_OF_RETRIES_EXCEEDED, lockKey, distributedLock.waitSeconds());
        }
        throw ApiException.createEx(ExceptionCodeEnum.REDIS_TRY_GET_LOCK_FAILURE, lockKey);
    }

}
